/* 
 * Copyright 2008 Jim Schaad and Paul Hoffman. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 
 * This software is provided by Jim Schaad and Paul Hoffman "as is" and
 * any express or implied warranties, including, but not limited to, the
 * implied warranties of merchantability and fitness for a particular
 * purpose are disclaimed. In no event shall Jim Schaad and Paul Hoffman
 * or contributors be liable for any direct, indirect, incidental,
 * special, exemplary, or consequential damages (including, but not
 * limited to, procurement of substitute goods or services; loss of use,
 * data, or profits; or business interruption) however caused and on any
 * theory of liability, whether in contract, strict liability, or tort
 * (including negligence or otherwise) arising in any way out of the use
 * of this software, even if advised of the possibility of such damage.
 */

#include <stdlib.h>
#include <memory.h>
#include <stdio.h>
#include <assert.h>

#include "A2C_Der.h"
#include "A2C_OctetString.h"
#include "A2C_Stream_Internal.h"
#include "A2C_Binary.h"

#define TRUE 1


A2C_CONST A2C_ITEM_HEADER A2C_OCTET_STRING_description = {
    sizeof(A2C_OCTET_STRING), 0
};

A2C_CONST A2C_DESCRIPTOR A2C_OCTET_STRING_descriptor = {
    A2C_zero,
    A2C_OCTET_STRING_release,
    (A2C_decoder_f) A2C_OCTET_STRING_decode_der,
    (A2C_encoder_f) A2C_OCTET_STRING_encode_der,
    (A2C_decoder_f) A2C_OCTET_STRING_decode_ber,
    (A2C_encoder_f) A2C_OCTET_STRING_encode_ber,
    (A2C_compare_f) A2C_OCTET_STRING_compare,
    (A2C_copy_f) A2C_OCTET_STRING_copy,
    (A2C_print_f) A2C_OCTET_STRING_print,
    A2C_OCTET_STRING_validate,
    &A2C_OCTET_STRING_description
};

static const A2C_TAG OctetStringTag = {A2C_TAG_CLASS_UNIVERSAL, 4};

/***************************  Global Functions *******************************************/

A2C_ERROR A2C_OCTET_STRING_release(PVOID pv, PC_A2C_DESCRIPTOR pdesc)
{
    A2C_OCTET_STRING * ps = (A2C_OCTET_STRING *) pv;

    unreferenced(pdesc);

    if (ps->data != 0) free(ps->data);
    ps->data = 0;

    return A2C_ERROR_Success;
}

/*
 *  Read from a DER string
 */

A2C_ERROR A2C_OCTET_STRING_decode_der(A2C_OCTET_STRING * pos, PC_A2C_DESCRIPTOR pdesc, int flags,
                                      A2C_CONTEXT * pcxt, A2C_TAG const * ptag, A2C_STREAM_MEMORY * pstm)
{
    A2C_LENGTH  cbLength;
    A2C_LENGTH  cbTL;
    int         err;
    int         fConstructed;
    int         iClass;
    A2C_TAG_VALUE iValue;

    unreferenced(pdesc);
    unreferenced(flags);
    unreferenced(pcxt);

    /*
     *  Grab the type and length of this item.  This function will without advancing
     *  the pointer if there are not sufficent bytes for the value in the buffer.
     */

    err = _A2C_get_tag_and_length(pstm, &iClass, &fConstructed, &iValue, &cbLength, &cbTL);
    if (err < 0) {
        goto ErrorExit;
    }

    /*
     *  Check that the tag matches what we expect it to be.
     */

    if (ptag != NULL) {
        if ((ptag->iClass != iClass) || (ptag->iValue != iValue)) {
            err = A2C_ERROR_tagMismatch;
            goto ErrorExit;
        }
    }
    else {
        if ((iClass != A2C_TAG_CLASS_UNIVERSAL) || (iValue != 4)) {
            err = A2C_ERROR_tagMismatch;
            goto ErrorExit;
        }
    }

    /*
     *  If this is a constructed value, then fail we don't have the
     *  correct code to deal with that in DER world.
     */
    
    if (fConstructed || (cbLength == -1)) {
        err = A2C_ERROR_malformedDer;
        goto ErrorExit;
    }

    /*
     *  See if we have enougth data
     */

    if (_A2C_Memory_Left(pstm) < cbLength + cbTL) {
        err = A2C_ERROR_needMoreData;
        goto ErrorExit;
    }

    /*
     *  Skip over the tag and length
     */

    _A2C_Memory_Skip(((A2C_STREAM_MEMORY *) pstm), cbTL);

    /*
     *  Allocate a buffer to read the result into
     */

    pos->data = malloc(cbLength);
    if (pos->data == NULL) {
        err = A2C_ERROR_outOfMemory;
        goto ErrorExit;
    }
    
    pos->length = cbLength;
    _A2C_Memory_Read(pstm, pos->data, cbLength);

    return A2C_ERROR_Success;

ErrorExit:
    if (pos->data != NULL) {
        free(pos->data);
        pos->data = NULL;
    }

    return err;
}


/*
 *  Write out a DER encoded bit string
 */

A2C_ERROR A2C_OCTET_STRING_encode_der(A2C_OCTET_STRING const * pos, PC_A2C_DESCRIPTOR pdesc, int flags, 
                                      A2C_CONTEXT * pcxt, A2C_TAG const * ptag, PA2C_STREAM pstm)
{
    int                 cb;
    int                 cbOut = 0;
    A2C_ERROR   err;

    unreferenced(pdesc);
    unreferenced(flags);
    unreferenced(pcxt);

    /*
     *  Were we told not to write out the tag?
     *  if not - then tag = UNIVERSAL, 3
     */

    if (ptag == NULL) {
        err = _A2C_write_tag(A2C_TAG_CLASS_UNIVERSAL, 0, 4, pstm, &cbOut);
    }
    else {
        err = _A2C_write_tag(ptag->iClass, 0, ptag->iValue, pstm, &cbOut);
    }
    if (err < A2C_ERROR_Success) {
        return err;
    }

    /*
     *  Write out the length byte
     */

    err = _A2C_write_length(pos->length, pstm, &cb);
    if (err < A2C_ERROR_Success) {
        return err;
    }

    /*
     *  Write out the bytes
     */
    
    return _A2C_write_bytes(pos->data, pos->length, pstm);
}

A2C_ERROR A2C_OCTET_STRING_decode_ber(A2C_OCTET_STRING * pos, PC_A2C_DESCRIPTOR pdesc, int flags,
                                      A2C_CONTEXT * pcxt, A2C_TAG const * ptag, A2C_STREAM_MEMORY * pstm)
{
    A2C_LENGTH          cbLength;
    A2C_LENGTH          cbTL;
    A2C_ERROR           err;
    int                 fConstructed;
    int                 iContext;
    int                 iClass;
    A2C_TAG_VALUE       iValue;
    A2C_STREAM_MEMORY * pstmDst = NULL;

    unreferenced(pdesc);
    
    /*
     *  Are we doing a restart operation?
     */

    if (flags & A2C_FLAGS_MORE_DATA) {
        A2C_CXT_ITEM * pcxtitem;
        /*
         *  Get the details we cached the last time
         *      and bump the last read count
         */

        iContext = A2C_ContextFrame(pcxt);
        pcxtitem = &pcxt->rgCxts[iContext];

        /*
         *  Grab the saved data
         */

        pstmDst = pcxtitem->pstm;

        /*
         *  If the stack is fully used - clear the restart flag
         */

        if (A2C_ContextTOS(pcxt)) {
            flags &= ~A2C_FLAGS_MORE_DATA;
        }
    }
    else {
        /*
         *  First time in
         */

        err = _A2C_get_tag_and_length(pstm, &iClass, &fConstructed, &iValue, &cbLength, &cbTL);
        if (err < 0) {
            goto ErrorExit;
        }

        /*
         *  Check that the tag matches what we expect it to be.
         */

        if (ptag != NULL) {
            if ((ptag->iClass != iClass) || (ptag->iValue != iValue)) {
                err = A2C_ERROR_tagMismatch;
                goto ErrorExit;
            }
        }
        else {
            if ((iClass != A2C_TAG_CLASS_UNIVERSAL) || (iValue != 4)) {
                err = A2C_ERROR_tagMismatch;
                goto ErrorExit;
            }
        }
        
        /*
         *  Create the destination stream
         */

        err = A2C_CreateMemoryStream((PA2C_STREAM *) &pstmDst);
        if (err < A2C_ERROR_Success) {
            goto ErrorExit;
        }

        /*
         *  Set us up for a restart
         */

        A2C_PushContext(pcxt, &iContext);

        /*
         *  We don't care about the tag
         */
    }


    err = _A2C_Recurse_GetLength(pstm, pstmDst, flags, pcxt, &OctetStringTag, NULL);
    if (err == A2C_ERROR_needMoreData) {
        /*
         *  Save the stream
         */

        pcxt->rgCxts[iContext].pstm = pstmDst;
        pstmDst = NULL;
    }
    else if (err >= A2C_ERROR_Success) {
        /*
         *  Grab the data from the stream and put it into the object
         */

        err = _A2C_Memory_GetData(pstmDst, &pos->data, &pos->length);
    }

ErrorExit:
    if (pstmDst != NULL) _A2C_Memory_Free(pstmDst);
    return err;
}    

A2C_ERROR A2C_OCTET_STRING_encode_ber(A2C_OCTET_STRING const * pos, PC_A2C_DESCRIPTOR pdesc, int flags, 
                                      A2C_CONTEXT * pcxt, A2C_TAG const * ptag, PA2C_STREAM pstm)
{
    /*
     *  We never do a constructed OCTET STRING here.
     *
     *  Pass off the work to the DER encoder
     */

    return A2C_OCTET_STRING_encode_der(pos, pdesc, flags, pcxt, ptag, pstm);
}

/* ---
/// <summary>
/// A2C_OCTET_STRING_print is the default octet string value print routine.
/// </summary>
/// <param name="pf">pointer to the boolean value</param>
/// <param name="pdesc">type descriptor - ignored</param>
/// <param name="iLevel">indentation level</param>
/// <param name="pstm">A2C_STREAM object to print into</param>
/// <return>1 on success, A2C_ERROR on failure.
--- */

A2C_ERROR A2C_OCTET_STRING_print(A2C_OCTET_STRING const * pos, PC_A2C_DESCRIPTOR pdesc, int iLevel, PA2C_STREAM pstm)
{
    A2C_LENGTH          i;
    PBYTE               pb;
    char *              pch;
    static const char   rgchHex[] = "0123456789ABCDEF";
    char                rgch[16*3+1];
    
    unreferenced(pdesc);

    memset(rgch, ' ', sizeof(rgch));
    rgch[sizeof(rgch)-1] = 0;

    /*
     *  Move down a line and dump out in groups of 16 bytes indented by one level
     */

    iLevel += 1;
    pstm->pfnWrite(pstm, (PBYTE) "\n", 1);

    for (i=0, pch = rgch, pb = pos->data; i<pos->length; i++, pch += 3, pb++) {
        pch[0] = rgchHex[(*pb)>>4];
        pch[1] = rgchHex[(*pb) & 0xf];

        if (i % 16 == 15) {
            _A2C_Indent(pstm, iLevel);
            pstm->pfnWrite(pstm, (PBYTE) rgch, sizeof(rgch)-1);
            pstm->pfnWrite(pstm, (PBYTE) "\n", 1);

            pch = rgch-3;
        }
    }

    *pch = 0;
    _A2C_Indent(pstm, iLevel);
    pstm->pfnWrite(pstm, (PBYTE) rgch, strlen(rgch));

    return 1;
}

int A2C_OCTET_STRING_compare(A2C_OCTET_STRING const * posLeft, A2C_OCTET_STRING const * posRight,
                             PC_A2C_DESCRIPTOR pdesc)
{
    int         cbLeft = posLeft->length;
    int         cbRight = posRight->length;
    int         i;

    /*
     *  We don't use this parameter
     */

    unreferenced(pdesc);

    /*
     *  Do the comparison
     */

    i = memcmp(posLeft->data, posRight->data, MIN(cbLeft, cbRight));
    if (i != 0) return i;

    return cbLeft - cbRight;
}

A2C_ERROR A2C_OCTET_STRING_copy(A2C_OCTET_STRING * posLeft, A2C_OCTET_STRING const * posRight, PC_A2C_DESCRIPTOR pdesc)
{
    /*
     *  We don't use this parameter
     */

    unreferenced(pdesc);

    /*
     *  Allocate the memory
     */

    posLeft->data = malloc(posRight->length);
    if (posLeft->data == NULL) {
        return A2C_ERROR_outOfMemory;
    }

    /*
     *  Copy over the data
     */

    posLeft->length = posRight->length;
    memcpy(posLeft->data, posRight->data, posRight->length);

    /*
     *  Return error code
     */

    return A2C_ERROR_Success;
}

/* ---
/// <summary>
/// If they have a length, they must have a buffer.
/// </summary>
/// <param name="pv">pointer to the string data</param>
/// <param name="pdesc">descriptor for the string</param>
/// <param name="pcxt">restart context information</param>
/// <param name="pstm">location to dump data</param>
/// <returns>A2C_ERROR code</returns>
--- */

A2C_ERROR A2C_OCTET_STRING_validate(PCVOID pv, PC_A2C_DESCRIPTOR pdesc, A2C_CONTEXT * pcxt, int * pf, PA2C_STREAM pstm)
{
    A2C_ERROR           err;
    int                 iFrame;
    A2C_OCTET_STRING *  pos = (A2C_OCTET_STRING *) pv;

    unreferenced(pdesc);

    /*
     *  If not already done - push our selfs on the stack
     */

    *pf = FALSE;
    if (A2C_ContextTOS(pcxt)) {
        err = A2C_PushContext2(pcxt, &iFrame, NULL, 0);
        if (err != A2C_ERROR_Success) return err;
    }
    else {
        iFrame = A2C_ContextFrame(pcxt);
    }

    /*
     *  Check that there is a buffer
     */
    
    if (A2C_ContextNum(pcxt, iFrame) == 0) {
        if ((pos->length > 0) && (pos->data == NULL)) {
            /*
             *  Mark as finished
             */
            
            A2C_SetContextNum(pcxt, iFrame, -1);

            return A2C_Validate_Print(pdesc, pcxt, "OCTET STRING", "Must have data if length > 0", pstm);
        }
    }

    /*
     *  No validate is done - return success
     */

    A2C_PopContext(pcxt, iFrame);

    *pf = TRUE;
    return A2C_ERROR_Success;
}
